package com.huoxingy.statics.controllers;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.Ordered;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.FastByteArrayOutputStream;
import org.springframework.util.StreamUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

/**
 * Created by hanxi on 2017/9/21.
 */
public class RequestLoggingFilter extends OncePerRequestFilter implements Ordered {
    private static Logger LOG = LoggerFactory.getLogger(RequestLoggingFilter.class);
    private static ThreadLocal<Long> requestBeginTime = new ThreadLocal<>();
    private static final int DEFAULT_MAX_PAYLOAD_LENGTH = 1024;

    private int maxPayloadLength = DEFAULT_MAX_PAYLOAD_LENGTH;
    private int maxResponseLength = 100;
    // Not LOWEST_PRECEDENCE, but near the end, so it has a good chance of catching all
    // enriched headers, but users can add stuff after this if they want to
    private int order = Ordered.LOWEST_PRECEDENCE - 10;

    public RequestLoggingFilter() {
        super();
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        MDC.put("traceId", request.getSession().getId());

        boolean isFirstRequest = !isAsyncDispatch(request);
        HttpServletRequest requestToUse = request;

        if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(request, getMaxPayloadLength());
        }

        HttpServletResponse responseToUse = response;
        if (!(response instanceof ContentCachingResponseWrapper)) {
            responseToUse = new ContentCachingResponseWrapper(response);
        }

        requestBeginTime.set(System.currentTimeMillis());

        try {
            filterChain.doFilter(requestToUse, responseToUse);
        } finally {
            if (!isAsyncStarted(requestToUse)) {
                long begin = requestBeginTime.get();
                long end = System.currentTimeMillis();

                long duration = end - begin;
                LOG.info(createMessage(requestToUse, responseToUse, duration));
            }
        }

        MDC.clear();
    }

    protected String createMessage(HttpServletRequest request, HttpServletResponse resp, long requestTime) {
        StringBuilder msg = new StringBuilder();
        // add request method
        msg.append(request.getMethod());
        // add request uri
        msg.append(" ").append(request.getRequestURI());
        // add query string
        String queryString = request.getQueryString();
        if (queryString != null) {
            msg.append('?').append(queryString);
        }
        // add request time
        // for example: 2017-10-24 03:12:11 GMT POST uri=/v1/sales/stores?size=10&page=0&sort=id,DESC 200 16
        msg.append(" ").append(resp.getStatus());
        msg.append(" ").append(requestTime);

        // add other content, for debug
        HttpSession session = request.getSession(false);
        if (session != null) {
            msg.append(";session=").append(session.getId());
        }

        msg.append(";headers=").append(new ServletServerHttpRequest(request).getHeaders());

        ContentCachingRequestWrapper wrapper =
                WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
        if (wrapper != null) {
            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                int length = Math.min(buf.length, getMaxPayloadLength());
                String payload;
                try {
                    payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
                    payload = payload.replaceAll("\n", "");
                } catch (UnsupportedEncodingException ex) {
                    payload = "[unknown]";
                }
                msg.append(";payload=").append(payload);
            }
        }

        ContentCachingResponseWrapper responseWrapper = WebUtils.getNativeResponse(resp, ContentCachingResponseWrapper.class);
        if (responseWrapper != null) {
            FastByteArrayOutputStream respStream = new FastByteArrayOutputStream(100);

            try {
                StreamUtils.copyRange(responseWrapper.getContentInputStream(), respStream, 0, getMaxResponseLength());
                responseWrapper.copyBodyToResponse();
            } catch (IOException e) {
                LOG.error("Fail to write response body back", e);
            }

            String payload;
            try {
                payload = new String(respStream.toByteArrayUnsafe(), responseWrapper.getCharacterEncoding());
                payload = payload.replaceAll("\n", "");
            } catch (IOException ex) {
                LOG.error(ex.getMessage());
                payload = "[unknown]";
            }

            msg.append(";response=").append(payload);

        }

        return msg.toString();
    }


    public int getMaxPayloadLength() {
        return maxPayloadLength;
    }

    public int getMaxResponseLength() {
        return maxResponseLength;
    }

    @Override
    public int getOrder() {
        return this.order;
    }
}